/*
 * ColeccionFlotas.cpp
 *
 *  Created on: 12/01/2010
 *      Author: San Martín, Matías I.
 */

#include "ColeccionFlotas.h"
#include "../Common/Nave.h"

#include "../../../Framework/IO/log.h"

using namespace framework::io;

namespace aplicacion {
namespace planeta {

ColeccionFlotas::ColeccionFlotas() {

}

ColeccionFlotas::~ColeccionFlotas() {
	size_t cantFlotas = _flotas.size();
	for (size_t i = 0; i < cantFlotas; ++i) {
		delete _flotas[i];
	}
}

void ColeccionFlotas::add(Flota* flota)
{
	_flotas.push_back(flota);
}

/** Permite eliminar todas las flotas cuyo dueño es el indicado
 *
 * @return cantidad de flotas eliminadas
 */
size_t ColeccionFlotas::removeByDuenio(const IdJugador& idDuenio)
{
	size_t cantEliminadas = 0;
	size_t cantFlotas = _flotas.size();
	for (size_t i = 0; i < cantFlotas; ++i) {
		Flota* flota = _flotas[i];
		//si la flota corresponde al jugador indicado, se elimina
		if (flota->getDuenio() == idDuenio) {
			delete flota;
			_flotas.erase(_flotas.begin() + i);
			cantFlotas --;
			i--;
			cantEliminadas++;
		}
	}//ciclo de recorrido de flotas

	return cantEliminadas;
}// removeByDuenio

bool ColeccionFlotas::contains(const IdFlota& idFlota) const
{
	size_t cantFlotas = _flotas.size();
	for (size_t i = 0; i < cantFlotas; ++i) {
		if (*_flotas[i] == idFlota)
			return true;
	}
	return false;
}

const t_idFlotasCantidadCollection* ColeccionFlotas::getCantidadNavesFlotasArribaron() const
{
	return &_flotasArribaron;
}

t_idFlotasCantidadCollection* ColeccionFlotas::getCantidadNavesFlotasArribaron()
{
	return &_flotasArribaron;
}

ssize_t ColeccionFlotas::getIndex(const IdFlota& idFlota) const
{
	size_t cantFlotas = _flotas.size();
	for (size_t i = 0; i < cantFlotas; ++i) {
		if (*_flotas[i] == idFlota)
			return i;
	}
	return -1;
}

const Flota& ColeccionFlotas::at(size_t index) const
{
	return *_flotas.at(index);
}

Flota& ColeccionFlotas::at(size_t index)
{
	return *_flotas.at(index);
}

void ColeccionFlotas::erase(size_t index)
{
	delete _flotas.at(index);
	_flotas.erase(_flotas.begin() + index);
}

size_t ColeccionFlotas::size() const
{
	return _flotas.size();
}

void ColeccionFlotas::addFlotaArriboNaves(const Flota& flota)
{
	t_idFlotasCantidadCollection::iterator fin = _flotasArribaron.end();
	for (t_idFlotasCantidadCollection::iterator i = _flotasArribaron.begin();
			i != fin; i++)
	{
		//si se encuentra la flota ya cargada, se actualiza su estado y se termina
		if (i->first == flota.getId())
		{
			i->second = flota.getCantidadNaves();
			return;
		}
	}

	//si la flota no estaba cargada en _flotasArribaron, se la carga
	t_parIdFlotaCantidad par;
	par.first = flota.getId();
	par.second = flota.getCantidadNaves();
	_flotasArribaron.push_back(par);
}

size_t ColeccionFlotas::setTiempo(T_TIEMPO tiempo)
{
	size_t retArribaron = 0;

	size_t cantFlotas = _flotas.size();
	//hago llegar todas las naves (no importa el orden)
	for (size_t i = 0; i < cantFlotas ; i++)
	{
		Flota* flota = _flotas[i];
		logger << LogType::LOG_DEBUG_1 << "naves flota antes de set tiempo(" <<
				tiempo << "): " << flota->getCantidadNaves() << logger.endl;
		size_t cantidadArribaron = flota->setTiempo(tiempo);
		logger << LogType::LOG_DEBUG_1 << "naves flota despues de set tiempo: "
				<< flota->getCantidadNaves() << logger.endl;
		retArribaron += cantidadArribaron;

		/* si arribaron naves de la flota se actualiza el listado de pares
		 * idFlota - cantidad arribada */
		if (cantidadArribaron > 0)
		{
			//se agrega/actualiza la flota que arribo naves
			this->addFlotaArriboNaves(*flota);

			//si la flota se quedo sin naves se elimina
			if (flota->getCantidadNaves() == 0)
			{
				delete flota;
				_flotas.erase(_flotas.begin() + i);
				cantFlotas --;
				i--;
			}
		}
	}

	return retArribaron;
}

size_t ColeccionFlotas::getCantidadArribaran(T_TIEMPO hastaTiempo) const
{
	size_t retArribaran = 0;

	size_t cantFlotas = _flotas.size();
	//verifico cuantas naves tienen que arribar al planeta
	for (size_t i = 0; i < cantFlotas ; i++)
	{
		retArribaran += _flotas[i]->arribaran(hastaTiempo);
	}

	return retArribaran;
}

ParIdJugadorCantidad ColeccionFlotas::getQuedoUltimo(T_TIEMPO hastaTiempo,
								const ParIdJugadorCantidad& jugadorInicial)
{
	T_TIEMPO tahora; //contendra el tiempo ultimo al cual se actualizaron las flotas

	/* Si no hay flotas por arribar o no hay diferencia de tiempo positiva
	 * con el tiempo indicado, el jugador que queda es el mismo que
	 * el que se indica (no arribaran naves y por lo tanto no podra cambiar)*/
	if (_flotas.size() == 0 || (tahora = (*_flotas.begin())->getAhora()) >= hastaTiempo)
		return jugadorInicial;

	ParIdJugadorCantidad jugadorQueda = jugadorInicial;
	typedef std::vector<std::list<const Nave*>*> t_vectorListasNaves;

	t_vectorListasNaves navesFlotas;
	size_t cantFlotas = _flotas.size();
	navesFlotas.reserve(cantFlotas);

	//mantiene la cantiad de naves total que arribarán
	size_t cantidadNaves = 0;
	//obtengo, para cada flota, las naves hasta el momento indicado
	for(size_t i = 0; i < cantFlotas ; ++i)
	{
		std::list<const Nave*>* navesHastaTiempo = _flotas[i]->getNavesPorTiempoHasta(hastaTiempo);
		cantidadNaves += navesHastaTiempo->size();
		navesFlotas.push_back(navesHastaTiempo);
	}

	//mientra hayan naves por arribar, se procesa
	while (cantidadNaves)
	{
		//mantiene el tiempo en que arribó la primera de las naves de las flotas
		T_TIEMPO tiempoPrimerNave = TIEMPO_MAX; //inicializo en el mayor tiempo posible

		/* mantiene la posición en el vector de listas de naves de la lista
		 * que contiene la última nave identificada como la primera en arribar
		 * (notar que esta nave corresponderá a la primera de la lista ya
		 * que se eliminan las naves de la lista al arribar) */
		size_t posFlotaUltimaNave = 0;

		//determino cual es la primer nave que debe arribar
		for (size_t i = 0; i < cantFlotas; i++)
		{
			//si quedan naves en la lista se verifica el tiempo de la primera
			if (navesFlotas[i]->size())
			{
				const Nave* primerNave = *(navesFlotas[i])->begin();
				/* si el tiempo es menor que el menor encontrado,
				 * este será la nave más próxima a arribar  */
				T_TIEMPO tiempoArribo = primerNave->getTiempoArribo(_flotas[i]->getDestino());
				if (tiempoArribo < tiempoPrimerNave)
				{
					posFlotaUltimaNave = i;
					tiempoPrimerNave = tiempoArribo;
				}
			}
		}//iteración sobre listas de naves

		//se "hace arribar" a la primer nave
		std::list<const Nave*>* listaNaveArribo = navesFlotas[posFlotaUltimaNave];

		listaNaveArribo->erase(listaNaveArribo->begin());
		cantidadNaves--;
		//actualizo el jugador asociado a la última nave que arribó
		IdJugador jugadorArribo = _flotas[posFlotaUltimaNave]->getId().getIdJugador();

		if (jugadorArribo == jugadorQueda.first)
		{
			jugadorQueda.second++;
		}else{
			if (jugadorQueda.second > 0)
			{
				jugadorQueda.second--;
			}else{
				jugadorQueda.first = jugadorArribo;
				jugadorQueda.second = 1;
			}
		}//condicion de jugador arribo es el jugador que esta
	}//ciclo de arribo de naves

	/* libero la memoria reservada para las listas de naves */
	for(size_t i = 0; i < cantFlotas ; ++i)
	{
		delete navesFlotas[i];
	}

	//hago arribar las naves estableciendo el tiempo al valor con el que se trabajó
	this->setTiempo(hastaTiempo);

	return jugadorQueda;
}//getArriboUltimo

/**
 * Permite obtener el tiempo en que arribaran las próximas "cantidadNaves"
 * de la colección (pertenecientes a <b>cualquiera</b> de las flotas)
 */
T_TIEMPO ColeccionFlotas::getTiempoArribaran(size_t cantidadNaves) const
{
	//si no llegaran a existir naves, no arribaran nunca la cantidad indicada
	T_TIEMPO retTiempoArribaran = TIEMPO_MAX;

	typedef std::vector<std::list<const Nave*>*> t_vectorListasNaves;
	typedef std::vector<const Flota*> t_vectorFlotas;

	t_vectorListasNaves navesFlotas;
	t_vectorFlotas	flotas;
	size_t cantFlotas = _flotas.size();
	navesFlotas.reserve(cantFlotas);


	//obtengo, para cada flota, las primeras (en tiempo de arribo) "cantidadNaves" naves
	for(size_t i = 0; i < cantFlotas ; ++i)
	{
		std::list<const Nave*>* naves = _flotas[i]->getNavesPorTiempoHastaCantidad(cantidadNaves);

		/* se agregan siempre porque por lo menos una nave tendran (de lo contrario
		 * no existiria la flota) */

		navesFlotas.push_back(naves);
		flotas.push_back(_flotas[i]);
	}

	//mantiene la cantiad de naves que "arribaron" al momento indicado en el tiempo
	size_t cantidadArribaron = 0;
	//mientra hayan naves por arribar, se procesa
	while (navesFlotas.size() > 0 && cantidadArribaron < cantidadNaves)
	{
		//mantiene el tiempo en que arribó la primera de las naves de las flotas
		T_TIEMPO tiempoPrimerNave = TIEMPO_MAX; //inicializo en el mayor tiempo posible

		/* mantiene la posición en el vector de listas de naves de la lista
		 * que contiene la última nave identificada como la primera en arribar
		 * (notar que esta nave corresponderá a la primera de la lista ya
		 * que se eliminan las naves de la lista al arribar) */
		size_t posFlotaUltimaNave = 0;

		//determino cual es la primer nave que debe arribar
		for (size_t i = 0; i < cantFlotas; i++)
		{
			const Nave* primerNave = *(navesFlotas[i])->begin();

			/* si el tiempo es menor que el menor encontrado,
			 * este será la nave más próxima a arribar  */
			T_TIEMPO tiempoArribo = primerNave->getTiempoArribo(flotas[i]->getDestino());
			if (tiempoArribo < tiempoPrimerNave)
			{
				posFlotaUltimaNave = i;
				tiempoPrimerNave = tiempoArribo;
			}
		}//iteración sobre listas de naves

		//se "hace arribar" a la primer nave
		std::list<const Nave*>* listaNaveArribo = navesFlotas[posFlotaUltimaNave];
		listaNaveArribo->erase(listaNaveArribo->begin());
		cantidadArribaron++;
		//actualizo el tiempo al tiempo de arribo de la nave
		retTiempoArribaran = tiempoPrimerNave;

		//si se vació la lista de naves, la elimino
		if (listaNaveArribo->size() == 0)
		{
			delete listaNaveArribo;
			navesFlotas.erase(navesFlotas.begin() + posFlotaUltimaNave);
			flotas.erase(flotas.begin() + posFlotaUltimaNave);
			cantFlotas--;
		}
	}

	//libero la memoria que pudiera quedar reservada para las listas de naves
	for (size_t i = 0; i < cantFlotas; i++)
	{
		delete navesFlotas[i];
	}

	return retTiempoArribaran;
}//getTiempoArribaran


} //namespace planeta
} //namespace aplicacion
